﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Text;

namespace SocialKit.LightRest
{
    /// <summary>
    /// Represents a multi part mime form.
    /// </summary>
    public class HttpMultipartMimeForm : IHttpContentCreator
    {
        const string CRLF = "\r\n";

        Collection<KeyValuePair<string, string>> values;

        /// <summary>
        /// Gets a collection of KeyValuePair&lt;string, string&gt; indicates the values of this form.
        /// </summary>
        public Collection<KeyValuePair<string, string>> Values
        {
            get
            {
                if (values == null)
                    values = new Collection<KeyValuePair<string, string>>();

                return values;
            }
        }

        Collection<HttpFormFile> files;

        /// <summary>
        /// Gets a collection ofSocialKit.LightRest.HttpFormFile indicates the files of this form.
        /// </summary>
        public Collection<HttpFormFile> Files
        {
            get
            {
                if (files == null)
                    files = new Collection<HttpFormFile>();

                return files;
            }
        }

        readonly string boundary;

        /// <summary>
        /// Initialize a new instance of SocialKit.LightRest.HttpMultipartMimeForm class.
        /// </summary>
        public HttpMultipartMimeForm()
        {
            this.boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x", CultureInfo.InvariantCulture);
        }


        /// <summary>
        /// Add a name and value pair into the values of this form.
        /// </summary>
        /// <param name="name">The name string.</param>
        /// <param name="value">The value string.</param>
        public void AddValue(string name, string value)
        {
            this.Values.Add(new KeyValuePair<string, string>(name, value));
        }

        /// <summary>
        /// Append a name and value pair into the values of this form then returns the current form.
        /// </summary>
        /// <param name="name">The name string.</param>
        /// <param name="value">The value string.</param>
        /// <returns>The current form.</returns>
        public HttpMultipartMimeForm AppendValue(string name, string value)
        {
            this.AddValue(name, value);

            return this;
        }


        /// <summary>
        /// Add an HttpFormFile into the files of this form.
        /// </summary>
        /// <param name="name">The name of an HttpFormFile.</param>
        /// <param name="file">The file name of an HttpFormFile.</param>
        public void AddFile(string name, string file)
        {
            this.Files.Add(new HttpFormFile(name, file));
        }

        /// <summary>
        /// Add an HttpFormFile into the files of this form.
        /// </summary>
        /// <param name="name">The name of an HttpFormFile.</param>
        /// <param name="file">The file name of an HttpFormFile.</param>
        /// <param name="fileContentType">The file content type string.</param>
        public void AddFile(string name, string file, string fileContentType)
        {
            this.Files.Add(new HttpFormFile(name, file, fileContentType));
        }

        /// <summary>
        /// Add an HttpFormFile into the files of this form.
        /// </summary>
        /// <param name="name">The name of an HttpFormFile.</param>
        /// <param name="file">The file name of an HttpFormFile.</param>
        /// <param name="fileContent">The file content stream.</param>
        public void AddFile(string name, string file, Stream fileContent)
        {
            this.Files.Add(new HttpFormFile(name, file, fileContent));
        }

        /// <summary>
        /// Add an HttpFormFile into the files of this form.
        /// </summary>
        /// <param name="name">The name of an HttpFormFile.</param>
        /// <param name="file">The file name of an HttpFormFile.</param>
        /// <param name="fileContent">The file content.</param>
        public void AddFile(string name, string file, byte[] fileContent)
        {
            this.Files.Add(new HttpFormFile(name, file, fileContent));
        }

        /// <summary>
        /// Add an HttpFormFile into the files of this form.
        /// </summary>
        /// <param name="name">The name of an HttpFormFile.</param>
        /// <param name="file">The file name of an HttpFormFile.</param>
        /// <param name="fileContentType">The file content type string.</param>
        /// <param name="fileContent">The file content stream.</param>
        public void AddFile(string name, string file, string fileContentType, Stream fileContent)
        {
            this.Files.Add(new HttpFormFile(name, file, fileContentType, fileContent));
        }

        /// <summary>
        /// Add an HttpFormFile into the files of this form.
        /// </summary>
        /// <param name="name">The name of an HttpFormFile.</param>
        /// <param name="file">The file name of an HttpFormFile.</param>
        /// <param name="fileContentType">The file content type string.</param>
        /// <param name="fileContent">The file content.</param>
        public void AddFile(string name, string file, string fileContentType, byte[] fileContent)
        {
            this.Files.Add(new HttpFormFile(name, file, fileContentType, fileContent));
        }


        /// <summary>
        /// Append an HttpFormFile into the files of this form then returns the current form.
        /// </summary>
        /// <param name="name">The name of an HttpFormFile.</param>
        /// <param name="file">The file path of an HttpFormFile.</param>
        /// <returns>The current form.</returns>
        public HttpMultipartMimeForm AppendFile(string name, string file)
        {
            this.AddFile(name, file);

            return this;
        }

        /// <summary>
        /// Append an HttpFormFile into the files of this form then returns the current form.
        /// </summary>
        /// <param name="name">The name of an HttpFormFile.</param>
        /// <param name="file">The file path of an HttpFormFile.</param>
        /// <param name="fileContentType">The file content type string.</param>
        /// <returns></returns>
        public HttpMultipartMimeForm AppendFile(string name, string file, string fileContentType)
        {
            this.AddFile(name, file, fileContentType);

            return this;
        }

        /// <summary>
        /// Append an HttpFormFile into the files of this form then returns the current form.
        /// </summary>
        /// <param name="name">The name of an HttpFormFile.</param>
        /// <param name="file">The file path of an HttpFormFile.</param>
        /// <param name="fileContent">The file content.</param>
        /// <returns></returns>
        public HttpMultipartMimeForm AppendFile(string name, string file, byte[] fileContent)
        {
            this.AddFile(name, file, fileContent);

            return this;
        }

        /// <summary>
        /// Append an HttpFormFile into the files of this form then returns the current form.
        /// </summary>
        /// <param name="name">The name of an HttpFormFile.</param>
        /// <param name="file">The file path of an HttpFormFile.</param>
        /// <param name="fileContent">The file content.</param>
        /// <returns></returns>
        public HttpMultipartMimeForm AppendFile(string name, string file, Stream fileContent)
        {
            this.AddFile(name, file, fileContent);

            return this;
        }

        /// <summary>
        /// Append an HttpFormFile into the files of this form then returns the current form.
        /// </summary>
        /// <param name="name">The name of an HttpFormFile.</param>
        /// <param name="file">The file path of an HttpFormFile.</param>
        /// <param name="fileContentType">The file content type string.</param>
        /// <param name="fileContent">The file content.</param>
        /// <returns></returns>
        public HttpMultipartMimeForm AppendFile(string name, string file, string fileContentType, Stream fileContent)
        {
            this.AddFile(name, file, fileContentType, fileContent);

            return this;
        }

        /// <summary>
        /// Append an HttpFormFile into the files of this form then returns the current form.
        /// </summary>
        /// <param name="name">The name of an HttpFormFile.</param>
        /// <param name="file">The file path of an HttpFormFile.</param>
        /// <param name="fileContentType">The file content type string.</param>
        /// <param name="fileContent">The file content.</param>
        /// <returns></returns>
        public HttpMultipartMimeForm AppendFile(string name, string file, string fileContentType, byte[] fileContent)
        {
            this.AddFile(name, file, fileContentType, fileContent);

            return this;
        }

        Encoding contentEncoding;

        #region IHttpContentCreator Members

        /// <summary>
        /// Gets or sets the encoding of content of this form.
        /// </summary>
        public Encoding ContentEncoding
        {
            get
            {
                if (contentEncoding == null)
                    contentEncoding = Encoding.UTF8;

                return this.contentEncoding;
            }

            set { this.contentEncoding = value; }
        }

        /// <summary>
        /// Gets the content type string of this form.
        /// </summary>
        public string ContentType
        {
            get { return "multipart/form-data; boundary=" + boundary; }
        }

        /// <summary>
        /// Returns a byte array contains the values and files of this form used for a request http body.
        /// </summary>
        /// <returns>A byte array.</returns>
        public byte[] CreateContent()
        {
            var ms = new MemoryStream();

            var boundaryBytes = ContentEncoding.GetBytes(CRLF + "--" + boundary + CRLF);
            var endBoundaryBytes = ContentEncoding.GetBytes(CRLF + "--" + boundary + "--" + CRLF);

            var valuesTemplate = CRLF + "--" + boundary + CRLF + "Content-Disposition: form-data; name=\"{0}\"" + CRLF + CRLF + "{1}";

            //write values
            foreach (var form in this.Values)
            {
                var formItem = string.Format(valuesTemplate, form.Key, form.Value);

                var formItemBytes = ContentEncoding.GetBytes(formItem);

                ms.Write(formItemBytes, 0, formItemBytes.Length);
            }

            //write boundary
            ms.Write(boundaryBytes, 0, boundaryBytes.Length);

            string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"" + CRLF + "Content-Type: {2}" + CRLF + CRLF;

            //write files
            for (int i = 0; i < this.Files.Count; i++)
            {
                var file = this.Files[i];

                var header = string.Format(headerTemplate, file.Name, file.FileName, file.FileContentType);
                var headerBytes = ContentEncoding.GetBytes(header);

                ms.Write(headerBytes, 0, headerBytes.Length);

                //write file content
                ms.Write(file.FileContent, 0, file.FileContent.Length);

                if (i < this.Files.Count - 1)
                    ms.Write(boundaryBytes, 0, boundaryBytes.Length);
                else
                    ms.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
            }

            return ms.ToArray();
        }

        #endregion
    }
}